
/**************************** item_31: 将文件间的编译依存关系降至最低 *****************************/

#include <string>

// C++ 并没有把 "将接口从实现中分离" 这事做得很好。Class 的定义式不只详细叙述了 class 接口，还包括十足的实现细目。例如：
class Person {
    public:
        Person(const std::string& name, const Date& birthday, const Address& addr);
        std::string name() const;
        std::string birthDate() const;
        std::string address() const;
        // ...

    private: 
        std::string theName;        //实现细目
        Date theBirthDate;
        Address theAddress;
};

// 这里的 class Person 无法通过编译 --- 如果编译器没有取得实现代码所用到的 class string，Date 和 Address 的定义式。这样的定义式通常由 #include 指示符提供，
// 所以 Person 定义文件的最上方很可能存在这样的东西：
#include <string>
#include "date.h"
#include "address.h"

// 这样一来便是在 Person 定义文件和其含入文件之间形成了一种编译依存关系。如果这些头文件中任何一个被改变，或这些头文件所依赖的其他头文件有任何改变，那么每一个含入 Person class
// 的文件就得重新编译，任何使用 Person class 的文件也必须重新编译。这样的连串编译依存关系会对许多项目造成难以形容的灾难。

// 为什么 C++ 坚持将 class 的实现细目置于 class 定义式中？为什么不这样定义 Person，将实现细目分开叙述？
namespace std {
    class string;           //前置声明（不正确）
}
class Date;                 //前置声明
class Address;              //前置声明
class Person {
    public:
        Person(const std::string& name, const Date& birthday, const Address& addr);
        std::string name() const;
        std::string birthDate() const;
        std::string address() const;
        // ...
}

// 如果可以这么做，Person 的客户就只需要在 Person 接口被修改过时才重新编译。
// 这个想法存在两个问题。第一，string 不是个 class，它是个 typedef（定义为basic_string<char>），不该尝试手工声明一部分标准库，应该仅仅使用适当的 #include 完成目的。
// 关于 "前置声明每一件东西"  的第二个（同时也是比较重要的）困难是，编译器必须在编译期间知道对象的大小。考虑这个：
int main()
{
    int x;                  //定义一个 int
    Person p(params);       //定义一个 Person
    // ...
}

// 当编译器看到 x 的定义式，它知道必须分配多少内存（通常位于stack内）才够持有一个 int。没问题，每个编译器都知道一个 int 有多大。当编译器看到 p 的定义式，它也知道必须分配足够
// 空间以放置一个 Person，但它如何知道一个 Person 对象有多大呢？编译器获得这项信息的唯一办法就是询问 class 定义式。然而如果 class 定义式可以合法地不列出细目，编译器如何知道
// 该分配多少空间？
// 在其他语言中编译器只分配足够空间给一个指针（用以指向该对象）使用。也就是说它们将上述代码视为这样子：
int main()
{
    int x;              //定义一个 int
    Person* p;          //定义一个指针指向 Person 对象
    // ...
}

// 这当然也是合法的 C++ 代码，所以你也可以自己玩玩 "将对象实现细目隐藏于一个指针背后" 的游戏。针对 Person 我们可以这样做：把 Person 分割为两个 classes，一个只提供接口，另
// 一个负责实现该接口。如果负责实现的那个所谓 implementation class 取名为 PersonImpl，Person 将定义如下：
#include <string>       //标准程序库组件不该被前置声明
#include <memory>       //此乃为了 std::shared_ptr 而含入

class PersonImpl;       //Pers实现类的前置声明                                 
class Date;             //Person接口用到的 classes 的前置声明
class Address;

class Person {
    public:
        Person(const std::string& name, const Date& birthday, const Address& addr);
        std::string name() const;
        std::string birthDate() const;
        std::string address() const;
        // ...
    private:
        std::shared_ptr<PersonImpl> pImpl;      //指针，指向实现物
}

// 在这里，main class（Person）内只含一个指针成员（这里使用shared_ptr），指向其实现类（PersonImpl）。这般设计常被称为 pimpl idiom（pimpl是 "pointer to 
// implementation" 的缩写）。这种 classes 内的指针名称往往就是 pImpl，就像上面代码那样。

// 这个分离的关键在于以 "声明的依存性" 替换 "定义的依存行"，那正是编译依存性最小化的本质：现实中让头文件尽可能自我满足，万一做不到，则让它与其他文件内的声明式（而非定义式）
// 相依。其他每一件事都源自于这个简单的设计策略：
// 1. 如果使用 object references 或 object pointers 可以完成任务，就不要使用 objects。你可以只靠一个类型声明式就定义出指向该类型的 references 和 pointers；
// 但是定义某类型的 objects，就需要用到该类型的定义式。
// 2. 如果能够，尽量以 class 声明式替换 class 定义式。当声明一个函数而它用到某个 class 时，你并不需要该 class 的定义式；纵使函数以 by value方式传递该类型的参数（或返回值）
// 亦然：
class Date;                     //class 声明式
Date today();                   //没问题 --- 这里并不需要 Date 的定义式
void clearAppointments(Date d);
// 3. 为声明式和定义式提供不同的头文件。为了促进严守上述准则，需要两个头文件，一个用于声明式，一个用于定义式。
#include "datefwd.h"            //这个头文件内声明（但未定义）class Date
Date today();
void clearAppointments(Date d);

// 只含声明式的那个头文件命名为 "datefwd.h"，命名方式取法 C++ 标准程序库头文件的 <iosfwd>。<iosfwd> 内含 iostream 各组件的声明式，其对应定义则分布在若干不同的头文件内，
// 包括 <sstream>, <streambuf>, <fstream> 和 <iostream>。

// 像 Person 这样使用 pimpl idiom 的class，往往被称为 Handle classes。这样的 classes 如何真正做点事情。办法之一是将他们的所有函数转交给相应的实现类并由后者完成实际工作。
// 例如下面的 Person 两个成员函数的实现：
#include "Person.h"         //我们正在实现 Person class，所以必须 #include 其 class 定义式
#include "PersonImpl.h"     //我们也必须 #include PersonImpl 的 class 定义式，否则无法调用其成员函数；
                            //注意，PersonImpl 有着和 Person 完全相同的成员函数，两者接口完全相同

Person::Person(const std::string& name, const Date& birthday, const Address& addr) {

}

std::string Person::name() const {
    return pImpl->name();
}

// Person 构造函数以 new 调用 PersonImpl 构造函数，以及 Person::name 函数内调用 PersonImpl::name，让 Person 变成一个 Handle class 并不会改变它做的事，只会改变它做事的方法。

// 另一个制作 Handle class 的办法是，令 Person 成为一种特殊的 abstract base class（抽象基类），称为 Interface class。这种 class 的目的是详细一一描述 derived classes
// 的接口，因此它通常不带成员变量，也没有构造函数，只有一个 virtual 析构函数以及一组 pure virtual 函数，用来叙述整个接口。

// 一个针对 Person 而写的 Interface class 或许看起来像这样：
class Person {
    public:
        virtual ~Person();
        virtual std::string name() const = 0;
        virtual std::string birthDate() const = 0;
        virtual std::string address() const = 0;
        // ...
};

// 这个 class 的客户必须以 Person 的 pointers 和 references 来撰写应用程序，因为它不可能针对 "内含 pure virtual 函数" 的 Person classes具现出实体。（然而却可能
// 对派生自 Person 的 classes 具现出实体）就像 Handle classes 的客户一样，除非 Interface class 的接口被修改否则其客户不需要重新编译。

// Interface class 的客户必须有办法为这种 class 创建新对象。他们通常调用一个特殊函数，此函数扮演 "真正将被具现化" 的那个 derived classes 的构造函数角色。这样的函数通常
// 成为 factory（工厂）函数或 virtual 构造函数。它们返回指针（或更为可取的智能指针），指向动态分配所得对象，而该对象支持 Interface class 的接口。这样的函数又往往在 Interface 
// class 内被声明为 static:
class Person {
    public:
        // ...
        static std::shared_ptr<Person> create(const std::string& name, const Date& birthday, const Address& addr); //返回一个 std::shared_ptr，指向
                                                                                                                // 一个新的 Person，并以给定之参数初始化
        // ...
};

// 客户会这样使用它们：
std::string  name;
Date dateOfBirth;
Address address;
// ...
// 创建一个对象，支持 Person 接口
std::shared_ptr<Person> pp(Person::create(name, dateOfBirth, address));
// ...
std::cout << pp->name()             //通过 Person 的接口使用这个对象
          << " was born on "
          << pp->birthDate()
          << " and now lives at "
          << pp->address();
//   ...
                                    //当 pp 离开作用域，对象会被自动删除

//支持 Interface class 接口的那个具象类必须被定义出来。而且真正的构造函数必须被调用。一切都在 virtual 构造函数实现码所在的文件内秘密发生。假设 Interface class Person
// 有个具象的 derived class RealPerson，后者提供集成而来的 virtual 函数的实现：
class RealPerson: public Person {
    public:
        RealPerson(const std::string& name, const Date& birthday, const Address& addr): theName(name), theBirthDate(birthDate), theAddress(addr)
        {}

    virtual ~RealPerson() {}
    std::string name() const;           //这些函数的实现并不显示于此，但他们很容易想象
    std::string birthDate() const;
    std::string address() const;
    
    private:
        std::string theName;
        Date theBirthDate;
        Address theAddress;
};

// 有了 RealPerson 之后，写出 Person::create 就真的一点也不稀奇了：
std::shared_ptr<Person> Person::create(const std::string& name, const Date& birthday, const Address& addr)
{
    return std::shared_ptr<Person>(new RealPerson(name, birthday, addr));
}

// 一个更现实的 Person::create 实现代码会创建不同类型的 derived class 对象，取决于诸如额外参数值、读自文件或数据库的数据、环境变量等等。
// RealPerson 示范实现 Interface class 的两个最常见机制之一：从 Interface class（Person）继承接口规格，然后实现出接口所覆盖的函数。Interface class 的第二个实现
// 法涉及多重继承。

// Handle classes 和 Interface classes 解除了接口和实现之间的耦合关系，从而降低文件间的编译依存性。但是（所有这些戏法得付出多少代价？）它使你运动期丧失若干速度，又让你为
// 每个对象超额付出若干内存。

// 在程序发展过程中使用 Handle classes 和 Interface classes 以求实现码有所变化时对其客户带来最小冲击。而当它们导致速度和/或大小差异过于重大以至于 classes 之间的耦合
// 相行之下不成为关键时，就以具象类替换 Handle classes 和 Interface classes。

// 总结：
// 1. 支持 "编译依存性最小化" 的一般构想是：相依于声明式，不要相依于定义式。基于此构想的两个手段的 Handle classes 和 Interface classes。
// 2. 程序库头文件应该以 "完全且仅有声明式" 的形式存在。这种做法不论是否涉及 templates 都适用。
